﻿#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
#endregion

namespace GameBase.Screens
{
    /// <summary>
    /// Management of one or more Screen instances.
    /// <para>It maintains a stack of Screen objects and calls their Update and Draw methods at appropriate times.</para>
    /// </summary>
    public class ScreenManager : DrawableGameComponent
    {
        #region Screen Stack

        // stack of game screens
        readonly List<Screen> screenStack;

        // points to the currently active screen (the topmost one)
        Screen currentScreen;

        // new screen added to the top of the stack...
        // ... use null for no transition
        Screen screenToAdd;

        // screen removed from the top of the stack
        Screen screenToRemove;

        // flags
        bool toAddScreen = false;
        bool toRemoveScreen = false;
        bool toClearStack = false;
        bool toExitGame = false;

        /// <summary>
        /// Adds a new Screen to the top of the stack.
        /// </summary>
        /// <param name="screen">New Screen added.</param>
        public void AddScreen(Screen screen)
        {
            // check if it's a valid screen
            if (screen != null)
            {
                toAddScreen = true;
                screenToAdd = screen;
            }
        }

        /// <summary>
        /// Removes a Screen from the stack.
        /// </summary>
        /// <param name="screen">Screen removed.</param>
        public void RemoveScreen(Screen screen)
        {
            // check if it's a valid screen
            if (screenStack.Contains(screen))
            {
                toRemoveScreen = true;
                screenToRemove = screen;
            }
        }

        /// <summary>
        /// Clears stack, then adding a new Screen to the top.
        /// </summary>
        /// <param name="screen">New Screen added.</param>
        public void Clear(Screen screen)
        {
            toClearStack = true;
            screenToAdd = screen;
        }

        /// <summary>
        /// Exits the game after a last transition.
        /// </summary>
        public void Exit()
        {
            toExitGame = true;
        }

        #endregion

        #region Screen Transition

        /// <summary>
        /// Defines the different states a transition passes through.
        /// </summary>
        public enum TransitionState 
        { 
            /// <summary>
            /// Screen becoming visible.
            /// </summary>
            TRANSITION_IN,
            /// <summary>
            /// Screen becoming invisible.
            /// </summary>
            TRANSITION_OUT,
            /// <summary>
            /// No transition.
            /// </summary>
            NO_TRANSITION
        };

        /// <summary>
        /// Gets the current transition state of the ScreenManager.
        /// </summary>
        public TransitionState State
        {
            get;
            private set;
        }

        // timers
        TimeSpan trTime = TimeSpan.Zero;

        /// <summary>
        /// Gets the time needed for a Screen to transition (in or out).
        /// </summary>
        public TimeSpan Time
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the current position of the Screen transition.
        /// <para>0.0f -> transition beginning; 1.0f -> transition ending</para>
        /// </summary>
        public float Position
        {
            get;
            private set;
        }

        // tell the topmost screen if available to become visible
        bool showCrScreen = false;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of this class.
        /// </summary>
        /// <param name="game">Game that the ScreenManager should be attached to.</param>
        public ScreenManager(Game game)
            : base(game)
        {
            // initialize the screen stack
            screenStack = new List<Screen>();

            // initial state of the screen manager
            State = TransitionState.NO_TRANSITION;
        }

        #endregion

        #region UnloadContent

        /// <summary>
        /// Called when graphics resources need to be unloaded.
        /// </summary>
        protected override void UnloadContent()
        {
            foreach (Screen screen in screenStack)
                screen.Dispose();
        }

        #endregion

        #region Update

        /// <summary>
        /// Called when the ScreenManager needs to be updated.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        public override void Update(GameTime gameTime)
        {
            // on Windows, a game is active if it is not minimized and has the current input focus...
            // on Xbox 360, the game is active if the user is not interacting with the Guide or Dashboard...
            if (!Game.IsActive)
                return;

            switch (State)
            {
                case TransitionState.NO_TRANSITION:
                    {
                        #region NO_TRANSITION

                        if (toAddScreen)
                        {
                            // stack empty?
                            if (screenStack.Count == 0)
                            {
                                State = TransitionState.TRANSITION_IN;
                                trTime = TimeSpan.Zero;
                                Position = 0.0f;
                                screenStack.Add(screenToAdd);
                                screenToAdd.Initialize();
                                screenToAdd.Show();
                                screenToAdd = null;
                                toAddScreen = false;
                            }
                            else
                            {
                                State = TransitionState.TRANSITION_OUT;
                                trTime = TimeSpan.Zero;
                                Position = 0.0f;
                                currentScreen.Hide();
                            }
                        }
                        else if (toRemoveScreen)
                        {
                            State = TransitionState.TRANSITION_OUT;
                            trTime = TimeSpan.Zero;
                            Position = 0.0f;
                            currentScreen.Hide();
                        }
                        else if (toClearStack)
                        {
                            // stack empty?
                            if (screenStack.Count == 0)
                            {
                                State = TransitionState.TRANSITION_IN;
                                trTime = TimeSpan.Zero;
                                Position = 0.0f;
                                screenStack.Add(screenToAdd);
                                screenToAdd.Initialize();
                                screenToAdd.Show();
                                screenToAdd = null;
                                toClearStack = false;
                            }
                            else
                            {
                                State = TransitionState.TRANSITION_OUT;
                                trTime = TimeSpan.Zero;
                                Position = 0.0f;
                                currentScreen.Hide();
                            }
                        }
                        else if (toExitGame)
                        {
                            State = TransitionState.TRANSITION_OUT;
                            trTime = TimeSpan.Zero;
                            Position = 0.0f;
                            currentScreen.Hide();
                        }

                        #endregion

                        break;
                    }
                case TransitionState.TRANSITION_OUT:
                    {
                        #region TRANSITION_OUT

                        // fade out complete?
                        if (trTime > Time)
                        {
                            if (toAddScreen)
                            {
                                screenStack.Add(screenToAdd);
                                screenToAdd.Initialize();
                                screenToAdd.Show();
                                screenToAdd = null;
                                toAddScreen = false;
                            }
                            else if (toRemoveScreen)
                            {
                                screenStack.Remove(screenToRemove);
                                screenToRemove.Dispose();
                                screenToRemove = null;
                                toRemoveScreen = false;

                                // tell the topmost screen if available to become visible
                                showCrScreen = true;
                            }
                            else if (toClearStack)
                            {
                                foreach (Screen screen in screenStack)
                                    screen.Dispose();
                                screenStack.Clear();
                                screenStack.Add(screenToAdd);
                                screenToAdd.Initialize();
                                screenToAdd.Show();
                                screenToAdd = null;
                                toClearStack = false;
                            }
                            else if (toExitGame)
                            {
                                Game.Exit();
                            }

                            // set next state
                            State = TransitionState.TRANSITION_IN;
                            trTime = TimeSpan.Zero;
                            Position = 0.0f;
                        }
                        else
                        {
                            trTime += gameTime.ElapsedGameTime;
                            Position = MathHelper.Clamp((float)(trTime.TotalMilliseconds / Time.TotalMilliseconds), 0.0f, 1.0f);
                        }

                        #endregion

                        break;
                    }
                case TransitionState.TRANSITION_IN:
                    {
                        #region TRANSITION_IN

                        // fade in complete?
                        if (trTime > Time)
                        {
                            State = TransitionState.NO_TRANSITION;
                            trTime = TimeSpan.Zero;
                            Position = 0.0f;
                        }
                        else
                        {
                            trTime += gameTime.ElapsedGameTime;
                            Position = MathHelper.Clamp((float)(trTime.TotalMilliseconds / Time.TotalMilliseconds), 0.0f, 1.0f);
                        }

                        #endregion

                        break;
                    }
            }

            // set the current screen as the topmost one
            currentScreen = (screenStack.Count != 0) ? screenStack[screenStack.Count - 1] : null;

            // update the current screen only if available
            if (currentScreen != null)
            {
                // disable player input during fade out
                currentScreen.InputEnabled = !(State == TransitionState.TRANSITION_OUT);

                if (showCrScreen)
                {
                    currentScreen.Show();
                    showCrScreen = false;
                }

                currentScreen.Update(gameTime);
            }
        }

        #endregion

        #region Draw

        /// <summary>
        /// Called when the ScreenManager needs to be drawn.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        public override void Draw(GameTime gameTime)
        {
            // draw the current screen only if available
            if (currentScreen != null)
                currentScreen.Draw(gameTime);
        }

        #endregion
    }
}
